package cn.hiapi.core.utils;

import org.apache.commons.lang3.StringUtils;
import org.bouncycastle.jcajce.provider.digest.Keccak;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.util.encoders.Hex;

import javax.crypto.Cipher;
import javax.crypto.Mac;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.security.AlgorithmParameters;
import java.security.KeyFactory;
import java.security.MessageDigest;
import java.security.Security;
import java.security.interfaces.RSAPrivateKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.Base64;

public class PasswordUtils {

    public static final byte[] AES_IV_BYTE = new byte[]{117, 111, 109, 122, 104, 121, 77, 107, 100, 84, 89, 79, 78, 106, 51, 76};
    public static final byte[] AES_KEY_BYTE = new byte[]{111, 70, 85, 116, 81, 69, 55, 109, 109, 121, 68, 121, 74, 79, 84, 53, 57, 110, 111, 58, 86, 52, 88, 50, 81, 70, 99, 111, 49, 67, 118, 108};


    public static final String AES_KEY = new String(AES_KEY_BYTE);
    public static final String AES_IV = new String(AES_IV_BYTE);

    public static final Charset UTF8 = StandardCharsets.UTF_8;

    public static final int AES_MODEL_ENCRYPT = Cipher.ENCRYPT_MODE;
    public static final int AES_MODEL_DECRYPT = Cipher.DECRYPT_MODE;


    private volatile static PasswordUtils instance;

    private PasswordUtils() {
        Security.addProvider(new BouncyCastleProvider());
    }


    public static PasswordUtils getInstance() {
        if (instance == null) {
            synchronized (PasswordUtils.class) {
                instance = new PasswordUtils();
            }
        }
        return instance;
    }


    public byte[] AES(byte[] data, byte[] key, byte[] iv, int model) throws Exception {
        SecretKeySpec spec = new SecretKeySpec(key, "AES");
        AlgorithmParameters parameters = AlgorithmParameters.getInstance("AES");
        parameters.init(new IvParameterSpec(iv));
        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7Padding", "BC");
        cipher.init(model, spec, parameters);
        return cipher.doFinal(data);
    }


    public byte[] AES(byte[] data, String key, String iv, int model) throws Exception {
        if (StringUtils.isEmpty(key)) throw new Exception("密钥Key不能空");
        if (key.length() > 32) key = key.substring(0, 32);
        if (StringUtils.isEmpty(iv)) throw new Exception("vi不能为空");
        if (iv.length() < 16) throw new Exception("iv字符串不能小于16");
        if (iv.length() > 16) iv = iv.substring(0, 16);
        byte[] keyByte = key.getBytes(StandardCharsets.UTF_8);
        byte[] ivByte = iv.getBytes(StandardCharsets.UTF_8);

        return AES(data, keyByte, ivByte, model);
    }


    public byte[] AES(byte[] data, int model) throws Exception {
        return this.AES(data, AES_KEY, AES_IV, model);
    }

    public byte[] aes_encrypt(byte[] data) throws Exception {
        return this.AES(data, AES_MODEL_ENCRYPT);
    }

    /**
     * aes 加密转 hex
     *
     * @param key
     * @param iv
     * @param text
     * @return
     * @throws Exception
     */
    public String aes_encrypt_hex(String key, String iv, String text) throws Exception {
        byte[] b = this.AES(text.getBytes(StandardCharsets.UTF_8), key, iv, AES_MODEL_ENCRYPT);
        return toHex(b);
    }

    /**
     * aes hex 解密
     *
     * @param key
     * @param iv
     * @param text
     * @return
     * @throws Exception
     */
    public String aes_decrypt_hex(String key, String iv, String text) throws Exception {
        byte[] data = Hex.decode(text);
        byte[] res = this.AES(data, key, iv, AES_MODEL_DECRYPT);
        return new String(res);
    }


    /**
     * AES 加密 在Base64编码
     *
     * @param
     * @return
     * @Date 2019/8/15 11:50
     * @Author AdinZ QQ1454269
     **/
    public String aes_encrypt_base64(String data, String key, String iv) throws Exception {
        byte[] bytes = this.AES(data.getBytes(StandardCharsets.UTF_8), key, iv, AES_MODEL_ENCRYPT);
        return Base64.getEncoder().encodeToString(bytes);
    }

    /**
     * 解密
     *
     * @param
     * @return
     * @Date 2019/8/15 12:00
     * @Author AdinZ QQ1454269
     **/
    public String aes_decrypt_base64(String data, String key, String iv) throws Exception {
        byte[] bytes = Base64.getDecoder().decode(data);
        byte[] result = this.AES(bytes, key, iv, AES_MODEL_DECRYPT);
        return new String(result);
    }


    /**
     * 解密
     *
     * @param data
     * @return
     * @throws Exception
     */
    public byte[] aes_decrypt(byte[] data) throws Exception {
        return this.AES(data, AES_MODEL_DECRYPT);
    }

    /**
     * aes 加密 base64编码
     *
     * @param data
     * @return
     * @throws Exception
     */
    public String aes_encrypt_base64(String data) throws Exception {
        byte[] bytes = this.aes_encrypt(data.getBytes(UTF8));
        return Base64.getEncoder().encodeToString(bytes);
    }

    public String aes_encrypt_base64_url(String data) throws Exception {
        byte[] bytes = this.aes_encrypt(data.getBytes(UTF8));
        return Base64.getUrlEncoder().encodeToString(bytes);
    }


    /**
     * aes 解密 base64编码
     *
     * @param data
     * @return
     * @throws Exception
     */
    public String aes_decrypt_base64(String data) throws Exception {
        byte[] bytes = Base64.getDecoder().decode(data);
        byte[] b = this.aes_decrypt(bytes);
        return new String(b);
    }

    public String aes_decrypt_base64_url(String data) throws Exception {
        byte[] bytes = Base64.getUrlDecoder().decode(data);
        byte[] b = this.aes_decrypt(bytes);
        return new String(b);
    }

    public String md5(String data) {
        try {
            MessageDigest md = MessageDigest.getInstance("MD5");
            byte[] bytes = md.digest(data.getBytes(UTF8));
            return toHex(bytes).toLowerCase();
        } catch (Exception e) {
            return null;
        }
    }

    public String sha1(String data) {
        try {
            MessageDigest md = MessageDigest.getInstance("SHA1");
            byte[] bytes = md.digest(data.getBytes(UTF8));
            return toHex(bytes).toLowerCase();
        } catch (Exception e) {
            return null;
        }
    }

    public String keccak256(String text) {
        try {
            byte[] input = text.getBytes(StandardCharsets.UTF_8);
            Keccak.DigestKeccak kecc = new Keccak.Digest256();
            kecc.update(input, 0, input.length);
            return Hex.toHexString(kecc.digest());
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    public byte[] keccak256(byte[] input) {
        try {
            Keccak.DigestKeccak kecc = new Keccak.Digest256();
            kecc.update(input, 0, input.length);
            return kecc.digest();
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }


    public static byte[] encryptSHA(byte[] data) throws Exception {
        MessageDigest sha = MessageDigest.getInstance("SHA");
        sha.update(data);
        return sha.digest();
    }


    private String toHex(byte[] bytes) {
        final char[] HEX_DIGITS = "0123456789ABCDEF".toCharArray();
        StringBuilder ret = new StringBuilder(bytes.length * 2);
        for (byte aByte : bytes) {
            ret.append(HEX_DIGITS[(aByte >> 4) & 0x0f]);
            ret.append(HEX_DIGITS[aByte & 0x0f]);
        }
        return ret.toString();
    }


    public String HmacSHA1(String key, String data) throws Exception {
        Mac mac = Mac.getInstance("HmacSHA1");
        SecretKeySpec secret_key = new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), "HmacSHA1");
        mac.init(secret_key);
        byte[] array = mac.doFinal(data.getBytes(StandardCharsets.UTF_8));
        StringBuilder sb = new StringBuilder();
        for (byte item : array) {
            sb.append(Integer.toHexString((item & 0xFF) | 0x100), 1, 3);
        }
        return sb.toString().toUpperCase();
    }


    public String HmacSHA256(String key, String data) throws Exception {
        byte[] array = this.hmacSha256(key, data);
        StringBuilder sb = new StringBuilder();
        for (byte item : array) {
            sb.append(Integer.toHexString((item & 0xFF) | 0x100), 1, 3);
        }
        return sb.toString().toUpperCase();
    }

    public byte[] hmacSha256(String key, String data) throws Exception {
        Mac mac = Mac.getInstance("HmacSHA256");
        SecretKeySpec secret_key = new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), "HmacSHA256");
        mac.init(secret_key);
        return mac.doFinal(data.getBytes(StandardCharsets.UTF_8));
    }

    public String base64(byte[] data) {
        return Base64.getEncoder().encodeToString(data) ;
    }


    public String sha256(String text) {
        MessageDigest messageDigest;
        try {
            messageDigest = MessageDigest.getInstance("SHA-256");
            messageDigest.update(text.getBytes(StandardCharsets.UTF_8));
            return toHex(messageDigest.digest());
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }


    public String base64_url_encode(String text) {
        try {
            return Base64.getUrlEncoder().encodeToString(text.getBytes(StandardCharsets.UTF_8));
        } catch (Exception ex) {
            return null;
        }
    }

    public String base64_url_decode(String text) {
        return new String(Base64.getUrlDecoder().decode(text));
    }

    /**
     * rsa 加密
     *
     * @param text
     * @param privateKey
     * @return
     * @throws Exception
     */
    public String rsa_encrypt(String text, String privateKey) throws Exception {
        RSAPrivateKey priKey = (RSAPrivateKey) KeyFactory.getInstance("RSA")
                .generatePrivate(new PKCS8EncodedKeySpec(privateKey.getBytes(StandardCharsets.UTF_8)));
        Cipher cipher = Cipher.getInstance("RSA");
        cipher.init(Cipher.ENCRYPT_MODE, priKey);
        byte[] doFinal = cipher.doFinal(text.getBytes(StandardCharsets.UTF_8));
        return Base64.getEncoder().encodeToString(doFinal);
    }


}
